///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef __BUOLA_GEOMETRY_CREGION_H__
#define __BUOLA_GEOMETRY_CREGION_H__

#include <buola/utility/crect.h>

namespace buola {

class CRegion
{
public:
    ///default constructor
    
    ///initializes the region to be empty
    CRegion()
        :   mBBox(0,0,0,0)
        ,   mData(&mBBox)
        ,   mCount(0)
    {}
    
    ///copy constructor
    CRegion(const CRegion &pRH);

    ///move constructor
    CRegion(CRegion &&pRH)
        :   mBBox(pRH.mBBox)
        ,   mCount(pRH.mCount)
    {
        if(pRH.mData==&pRH.mBBox)
            mData=&mBBox;
        else
        {
            mData=pRH.mData;
            pRH.mData=&pRH.mBBox;
        }
        pRH.mCount=0;
    }
    
    ///initializes to unbounded rectangle
    CRegion(NUnbounded)
        :   mBBox(nUnbounded)
        ,   mData(&mBBox)
        ,   mCount(1)
    {}
    
    ///initialize to single rectangle
    explicit CRegion(const CRect_i &pRect)
        :   mBBox(pRect)
        ,   mData(&mBBox)
        ,   mCount(1)
    {}
    
    ///initialize to single rectangle
    explicit CRegion(const CRect_d &pRect)
        :   mBBox(pRect)
        ,   mData(&mBBox)
        ,   mCount(1)
    {}
    
private:
    ///internal constructor
    CRegion(const CRect_i &pBBox,CRect_i *pData,std::size_t pCount)
        :   mBBox(pBBox)
        ,   mCount(pCount)
    {
        if(mCount>1)
        {
            mData=pData;
        }
        else
        {
            mData=&mBBox;
            free(pData);
        }
    }
    
public:
    ///destructor
    ~CRegion()
    {
        if(mData!=&mBBox) free(mData);
    }

    ///resets the region to be empty
    
    ///Doesn't modify the capacity of the region
    void clear()
    {
        mBBox={0,0,0,0};
        mCount=0;
    }

    bool empty() const {   return (mCount==0); }

    bool Unbounded() const
    {
        return (mCount==1&&mBBox.Unbounded());
    }
    
    ///returns the bounding rectangle
    
    ///If the region is empty, returns an empty rectangle (l==r,t==b)
    const CRect_i &BoundingBox() const    {   return mBBox;  }

    ///returns the number of rectangles in the region
    std::size_t size() const  {   return mCount;  }

    ///returns an iterator to the first rectangle in the region
    const CRect_i *begin() const   {   return mData;           }
    ///returns an iterator past the last rectangle in the region
    const CRect_i *end() const     {   return mData+mCount;    }

    ///queries whether the given point is within the region
    bool Contains(const CPoint_i &pPoint) const;
    ///queries whether the given point is within the region
    bool Contains(const CPoint_d &pPoint) const   {   return Contains(CPoint_i(pPoint)); }
    
    ///offsets the region by offsetting all its rectangles
    void Offset(const CPoint_i &pOffset);
    ///offsets the region by offsetting all its rectangles
    void Offset(const CPoint_d &pOffset)      {   Offset(CPoint_i(pOffset)); }
    
    ///copy assignment operator
    CRegion &operator=(const CRegion &pRH);
    
    ///assigns a single rectangle to the region
    CRegion &operator=(const CRect_i &pRect);                   
    
    ///assigns a single rectangle to the region
    CRegion &operator=(const CRect_d &pRect)
    {   
        return *this=CRect_i(pRect);
    }
    
    ///move assignment operator
    CRegion &operator=(CRegion &&pRH)
    {
        mBBox=pRH.mBBox;
        
        if(pRH.mData==&pRH.mBBox)
        {
            if(mData!=&mBBox)
            {
                pRH.mData=mData;
                mData=&mBBox;
            }
        }
        else
        {
            if(mData==&mBBox)
            {
                mData=pRH.mData;
                pRH.mData=&pRH.mBBox;
            }
            else
            {
                using std::swap;
                swap(mData,pRH.mData);
            }
        }
        
        mCount=pRH.mCount;
        
        return *this;
    }

    ///equality comparison operator
    bool operator==(const CRegion &pRH) const;
    ///inequality comparison operator
    bool operator!=(const CRegion &pRH) const {   return !operator==(pRH);    }
    
    ///union-assignment operator
    CRegion &operator|=(const CRegion &pRegion);
    ///union-assignment operator
    CRegion &operator|=(const CRect_i &pRect);
    
    ///union-assignment operator
    CRegion &operator|=(const CRect_d &pRect)
    {   
        return operator|=(CRect_i(pRect));
    }
        
    ///subtraction-assignment operator
    CRegion &operator-=(const CRegion &pRegion);
    ///subtraction-assignment operator
    CRegion &operator-=(const CRect_i &pRect);
    
    ///subtraction-assignment operator
    CRegion &operator-=(const CRect_d &pRect)
    {   
        return operator-=(CRect_i(pRect));
    }
    
    ///intersection-assignment operator
    CRegion &operator&=(const CRegion &pRegion);
    ///intersection-assignment operator
    CRegion &operator&=(const CRect_i &pRect);
    
    ///intersection-assignment operator
    CRegion &operator&=(const CRect_d &pRect)
    {   
        return operator&=(CRect_i(pRect));
    }
    
    ///offset-assignment operator
    CRegion &operator+=(const CPoint_i &pOffset)
    {
        Offset(pOffset);
        return *this;
    }
    
    ///offset-assignment operator
    CRegion &operator+=(const CPoint_d &pOffset)
    {
        Offset(pOffset);
        return *this;
    }
    
    ///union operator
    CRegion operator|(const CRegion &pRegion) const;
    ///union operator
    CRegion operator|(const CRect_i &pRect) const;
    
    ///union operator
    CRegion operator|(const CRect_d &pRect) const
    {
        return operator|(CRect_i(pRect));
    }
    
    ///subtraction operator
    CRegion operator-(const CRegion &pRegion) const;
    ///subtraction operator
    CRegion operator-(const CRect_i &pRect) const;
    
    ///subtraction operator
    CRegion operator-(const CRect_d &pRect) const
    {
        return operator-(CRect_i(pRect));
    }
    
    ///intersection operator
    CRegion operator&(const CRegion &pRegion) const;    
    ///intersection operator
    CRegion operator&(const CRect_i &pRect) const;
    
    ///intersection operator
    CRegion operator&(const CRect_d &pRect) const
    {
        return operator&(CRect_i(pRect));
    }
    
    ///xor operator
    CRegion operator^(const CRegion &pRegion) const;    
    ///xor operator
    CRegion operator^(const CRect_i &pRect) const;
    
    ///xor operator
    CRegion operator^(const CRect_d &pRect) const
    {
        return operator^(CRect_i(pRect));
    }
    
    ///offset operator
    CRegion operator+(const CPoint_i &pOffset) const
    {
        CRegion lNew(*this);
        lNew+=pOffset;
        return lNew;
    }
    
    ///offset operator
    CRegion operator+(const CPoint_d &pOffset) const
    {
        CRegion lNew(*this);
        lNew+=pOffset;
        return lNew;
    }
    
private:
    CRect_i mBBox;    //it actually contains the data if mCount==1
    CRect_i *mData;
    std::size_t mCount;
};

inline io::CTextWriter &operator<<(io::CTextWriter &pWriter,const CRegion &pRegion)
{
    pWriter << pRegion.size() << " rectangles\n";
    
    for(const CRect_i &r : pRegion)
        pWriter << r << "\n";
        
    return pWriter;
}

/*namespace buola*/ }

#endif